// SPDX-License-Identifier: GPL-2.0+
/*
 * (C) Copyright 2013
 * David Feng <fenghua@phytium.com.cn>
 *
 * (C) Copyright 2016
 * Alexander Graf <agraf@suse.de>
 */

#include <stdint.h>
#include <stdio.h>
#include <stdbool.h>
#include <aarch64.h>
#include <kernel.h>
#include "ft_io.h"
#include "cache.h"

extern int AsmInvaliDateL3dcache();
extern void __asm_invalidate_dcache_all();
extern void __asm_invalidate_dcache_range(uint64_t start, uint64_t end);
extern void __asm_flush_dcache_all();
extern void __asm_flush_dcache_range(uint64_t start, uint64_t end);
extern void __asm_invalidate_icache_all();
extern void __asm_invalidate_l3_icache();
extern void __asm_invalidate_tlb_all();

/* FLUSH L3 CASHE */
#ifdef CONFIG_USE_L3CACHE
#define HNF_BASE (unsigned long)(0x3A200000)
#define HNF_COUNT 0x8
#define HNF_PSTATE_REQ (HNF_BASE + 0x10)
#define HNF_PSTATE_STAT (HNF_BASE + 0x18)
#define HNF_PSTATE_OFF 0x0
#define HNF_PSTATE_SFONLY 0x1
#define HNF_PSTATE_HALF 0x2
#define HNF_PSTATE_FULL 0x3
#define HNF_STRIDE 0x10000
#endif

int AsmFlushL3Dcache(void)
{

#ifdef CONFIG_USE_L3CACHE
    int i, pstate;

    for (i = 0; i < HNF_COUNT; i++)
        FtOut64(HNF_PSTATE_REQ + i * HNF_STRIDE, HNF_PSTATE_SFONLY);
    for (i = 0; i < HNF_COUNT; i++)
    {
        do
        {
            pstate = FtIn64(HNF_PSTATE_STAT + i * HNF_STRIDE);
        } while ((pstate & 0xf) != (HNF_PSTATE_SFONLY << 2));
    }

    for (i = 0; i < HNF_COUNT; i++)
        FtOut64(HNF_PSTATE_REQ + i * HNF_STRIDE, HNF_PSTATE_FULL);

#endif
    return 0;
}

int AsmInvaliDateL3dcache(void)
{
#ifdef CONFIG_USE_L3CACHE
    int i, pstate;

    for (i = 0; i < HNF_COUNT; i++)
        FtOut64(HNF_PSTATE_REQ + i * HNF_STRIDE, HNF_PSTATE_SFONLY);

    for (i = 0; i < HNF_COUNT; i++)
    {
        do
        {
            pstate = FtIn64(HNF_PSTATE_STAT + i * HNF_STRIDE);
        } while ((pstate & 0xf) != (HNF_PSTATE_SFONLY << 2));
    }

    for (i = 0; i < HNF_COUNT; i++)
        FtOut64(HNF_PSTATE_REQ + i * HNF_STRIDE, HNF_PSTATE_FULL);
#endif
    return 0;
}

void FCacheDCacheEnable(void)
{
    uint64_t reg;

    reg = AARCH64_READ_SYSREG(sctlr_el1);

    if (!(reg & SCTLR_ELx_C))
    {
        FCacheDCacheInvalidate();
        __asm_invalidate_tlb_all();
        reg |= SCTLR_ELx_C;
        AARCH64_WRITE_SYSREG(sctlr_el1, reg);
    }
}

void FCacheDCacheDisable(void)
{
    uint64_t reg;
    reg = AARCH64_READ_SYSREG(sctlr_el1);

    if (!(reg & SCTLR_ELx_C))
        return;

    reg &= (~SCTLR_ELx_C);
    AARCH64_WRITE_SYSREG(sctlr_el1, reg);
    INTERRUPT_DISABLE();

    __asm_flush_dcache_all();

#ifdef CONFIG_USE_L3CACHE
    int i, pstate;
    for (i = 0; i < HNF_COUNT; i++)
        FtOut64(HNF_PSTATE_REQ + i * HNF_STRIDE, HNF_PSTATE_SFONLY);
    for (i = 0; i < HNF_COUNT; i++)
    {
        do
        {
            pstate = FtIn64(HNF_PSTATE_STAT + i * HNF_STRIDE);
        } while ((pstate & 0xf) != (HNF_PSTATE_SFONLY << 2));
    }

    for (i = 0; i < HNF_COUNT; i++)
        FtOut64(HNF_PSTATE_REQ + i * HNF_STRIDE, HNF_PSTATE_FULL);
#endif

    __asm_invalidate_tlb_all();
    INTERRUPT_ENABLE();
}

void FCacheDCacheInvalidate(void)
{
    INTERRUPT_DISABLE();
    __asm_invalidate_dcache_all();
    AsmInvaliDateL3dcache();
    INTERRUPT_ENABLE();
}

void FCacheDCacheInvalidateRange(uintptr adr, u32 len)
{
    INTERRUPT_DISABLE();
    __asm_invalidate_dcache_range(adr, adr + len);
    INTERRUPT_ENABLE();
}

void FCacheDCacheFlush(void)
{
    INTERRUPT_DISABLE();
    __asm_flush_dcache_all();
    AsmFlushL3Dcache();
    INTERRUPT_ENABLE();
}

void FCacheDCacheFlushRange(uintptr adr, u32 len)
{
    INTERRUPT_DISABLE();
    __asm_flush_dcache_range(adr, adr + len);
    INTERRUPT_ENABLE();
}

void FCacheICacheEnable(void)
{
    uint64_t reg;

    reg = AARCH64_READ_SYSREG(sctlr_el1);

    if (!(reg & SCTLR_ELx_I))
    {
        FCacheICacheInvalidate();
        reg |= SCTLR_ELx_I;
        AARCH64_WRITE_SYSREG(sctlr_el1, reg);
    }
}

void FCacheICacheDisable(void)
{
    uint64_t reg;

    reg = AARCH64_READ_SYSREG(sctlr_el1);
    reg &= ~SCTLR_ELx_I;
    AARCH64_WRITE_SYSREG(sctlr_el1, reg);
}

void FCacheICacheInvalidate(void)
{
    INTERRUPT_DISABLE();
    __asm_invalidate_icache_all();
    __asm_invalidate_l3_icache();
    INTERRUPT_ENABLE();
}